import { platform } from "os";
import { request } from "https";
import { posix, resolve, relative, parse } from "path";
import { readFileSync, promises } from "fs";
import fastGlob from "fast-glob";
import svgo from "svgo";
import ansi from "ansi-colors";

import type { Plugin } from "vite";
import type { Config as SvgConfig } from "svgo";

const isWindows = platform() === "win32";
const slash = (p: string): string => p.replace(/\\/g, "/");
const normalizePath = (id: string): string => posix.normalize(isWindows ? slash(id) : id);
const randomIp = () => Array.from({ length: 4 }).fill(0).map(() => Number.parseInt(`${Math.random() * 255}`)).join(".");
const shuffleArray = (arr: any[]) => arr.sort(() => Math.random() - 0.5);
const ROOT = normalizePath(process.cwd());
const { cyan, magenta, greenBright, blueBright, gray, grey, red, green, yellow, blue, dim, bold } = ansi
interface OptionsType {
  outDir?: string; // 默认根目录dist文件夹
  svgConfig?: SvgConfig; // svg 配置
  max?: number; // 图片最大压缩
  quiet?: boolean; // 是否输出压缩信息
  pass?: boolean;
}
export const viteTinyfy = (options: OptionsType = {}): Plugin => {
  const outDir = options.outDir || "dist";
  const max = options.max || 5_242_880;
  const quiet = options.quiet ?? false;
  const pass = options.pass || false;
  const log = (str: string) => {
    if (quiet) return;
    console.log(str)
  }
  const svgConfig = Object.assign(options.svgConfig || {}, {
    multipass: true,
    plugins: [
      {
        name: "preset-default",
        params: {
          overrides: {
            cleanupNumericValues: false,
            removeViewBox: false,
          },
        },
      },
      "sortAttrs",
      {
        name: "addAttributesToSVGElement",
        params: {
          attributes: [{ xmlns: "http://www.w3.org/2000/svg" }],
        },
      },
    ],
  });

  function applySVGO(inputPath: string, outPath: string) {
    return new Promise(async (resolve, reject) => {
      const data = readFileSync(inputPath, 'utf8')
      const stats = await promises.stat(inputPath);

      if (stats.size > max) {
        log(red(`⚠️ wei xian[file]${inputPath} --> TinyfyProcessor not support oversize ${stats.size}, expected ${max}`));
        return resolve(false);
      }

      const result = svgo.optimize(data, Object.assign({ path: inputPath }, svgConfig)).data;
      promises.writeFile(outPath, result).then(() => resolve({ path: inputPath, size: stats.size })).catch(() => reject())
    })

  };
  function fetchTingfy(file: string): Promise<{ output: { url?: string } }> {
    return new Promise(async (resolve, reject) => {
      const http = request({
        headers: {
          "Cache-Control": "no-cache",
          "Content-Type": "image/png",
          "Postman-Token": Date.now(),
          "User-Agent":
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",
          "X-Forwarded-For": randomIp(),
        },
        hostname: "tinypng.com",
        method: "POST",
        path: "/backend/opt/shrink",
        rejectUnauthorized: false,
      }, (res) => {
        const chunks: Uint8Array[] = [];
        res.on("data", (chunk: Uint8Array) => {
          chunks.push(chunk);
        });

        res.on("end", () => {
          const body = Buffer.concat(chunks);
          let object: any;
          try {
            object = JSON.parse(body.toString());
            object.error ? reject(object.message) : resolve(object);
          } catch {
            reject(body);
          }
        });
      });
      http.on("error", (error) => {
        reject(error);
      });
      http.write(file, "binary");
      http.end();
    })
  }
  function download(url: string): Promise<string> {
    const options = new URL(url);
    return new Promise((resolve, reject) => {
      const http = request(options, (res) => {
        let file = "";
        res.setEncoding("binary");
        res.on("data", (chunk) => {
          file += chunk;
        });
        res.on("end", () => resolve(file));
      });
      http.on("error", (error) => {
        reject(error);
      });
      http.end();
    });
  }
  function applyTinyfy(inputPath: string, outPath: string) {
    return new Promise(async (resolve, reject) => {
      const file = await promises.readFile(inputPath, "binary");
      const stats = await promises.stat(inputPath);

      if (stats.size > max) {
        log(red(`[file]${inputPath} --> TinyfyProcessor not support oversize ${stats.size}, expected ${max}`));
        return resolve(false);
      }

      const { output = {} } = await fetchTingfy(file);

      if (!output.url) {
        log(red("TinyfyProcessor upload fail"))
        return resolve(false);
      };

      const data = await download(output.url);
      if (!data) {
        log(red("TinyfyProcessor download fail"))
        return resolve(false);
      };

      promises.writeFile(outPath, data, "binary").then(() => resolve({ path: inputPath, size: stats.size })).catch(() => reject())
    })
  }

  return {
    name: "vite-plugin-sharp",
    apply: "build",
    enforce: "post",
    async closeBundle() {
      if (pass) return;

      // 压缩图片
      const files = fastGlob.sync([
        "**.png",
        "**.jpg",
        "**.jpeg",
        "**.gif",
        "**.tiff",
        "**.webp",
        "**.svg",
        "**.avif",
      ], { cwd: outDir });
      if (!files.length) return;

      // 压缩
      const sizesMap = new Map<string, { newSize: number, oldSize: number, path: string, percentChange: string }>();
      for (let index = 0; index < files.length; index++) {
        const filePath = files[index];
        const lodFilePath = resolve(outDir, filePath);
        const engine = /\.svg$/.test(filePath) ? applySVGO : applyTinyfy;

        // 压缩
        const res: any = await engine(lodFilePath, lodFilePath);
        if (res && res.path) {
          const stat = await promises.stat(res.path);
          const path = relative(ROOT, res.path);
          sizesMap.set(path, {
            newSize: stat.size / 1024,
            oldSize: res.size / 1024,
            percentChange: `${(((stat.size - res.size) * 100) / res.size).toFixed(2)}%`,
            path: path,
          })
        }
      }

      if (sizesMap.size == files.length) {
        log(`\n✨ ${cyan('[vite-plugin-tinyfy]')} - optimized images successfully: `);
      } else {
        log(`\n✨ ${red('[vite-plugin-tinyfy]')} - errors during optimization: ${bold(greenBright(`${files.length - sizesMap.size}/${files.length}`))}`);
      }

      const keyLengths: number[] = Array.from(sizesMap.keys(), (name: string) => name.length);
      const valueLengths: number[] = Array.from(sizesMap.values(), (value: any) => value.percentChange.length);
      const maxKeyLength: number = Math.max(...keyLengths);
      const valueKeyLength: number = Math.max(...valueLengths);
      const colors = shuffleArray([cyan, magenta, grey, blueBright, gray, red, green, yellow, blue]);

      let totalOriginalSize: number = 0;
      let totalSavedSize: number = 0;
      let index = 0;

      sizesMap.forEach((value, name) => {
        const { path, percentChange, oldSize, newSize } = value;
        const { dir, base } = parse(path);
        const randomColor = (str: string) => colors[index](str);

        log(
          dim(dir + '/') + randomColor(base) +
          ' '.repeat(2 + maxKeyLength - name.length) +
          bold(randomColor(`${percentChange} ${' '.repeat(valueKeyLength - percentChange.length)}`)) +
          ' ' +
          dim(`${oldSize.toFixed(2)} kB ⭢  ${newSize.toFixed(2)} kB`)
        );
        totalOriginalSize += oldSize;
        totalSavedSize += oldSize - newSize;
        if (index >= (colors.length - 1)) {
          index = 0
        } else {
          index++;
        }
      });
      if (totalSavedSize > 0) {
        const savedText = `${totalSavedSize.toFixed(2)}kB`;
        const originalText = `${totalOriginalSize.toFixed(2)}kB`;
        const savingsPercent = `${Math.round((totalSavedSize / totalOriginalSize) * 100)}%`;
        log(`\n💰 total savings = ${green(savedText)}/${green(originalText)} ≈ ${bold(blue(savingsPercent))}`);
      }
    },
  }
}

export default viteTinyfy